#include "CurlManager.h"
#include "LogEx.h"
#include "StackTrace.h"
#ifdef _USE_CURL_
#include <curl/curl.h>
using namespace std;
using namespace SGLib;

SINGLETON_CLASS_INIT(CCurlManager);

CCurlObject::CCurlObject()
{
    m_worker = NULL;
    m_isStop = true;
}

CCurlObject::~CCurlObject()
{
}

bool CCurlObject::Start()
{
    if( m_worker )
    {
        return true;
    }

    CURL *curl = curl_easy_init();
    if( !curl )
    {
        return false;
    }

    m_isStop = false;
    m_worker = new CThread( this );
    if( m_worker == NULL || !m_worker->Start() )
    {
        SAFE_DELETE( m_worker );
        return false;
    }

    LOG_INFO( "CCurlObject Start" );
    return true;
}

void CCurlObject::Stop()
{
    if( !m_isStop && m_worker )
    {
        m_isStop = true;
        m_workSignal.SetEvent();
        m_worker->Stop();
        SAFE_DELETE( m_worker );
    }
    LOG_INFO( "CCurlObject Stop" );
}

void CCurlObject::Run()
{
    LOG_INFO( "CCurlObject Start" );
    while( !m_isStop )
    {
        m_workSignal.Wait();

        if( m_isStop )
        {
            break;
        }

        while( 1 )
        {
            curlReq _req;
            {
                CGuardLock<CLock> g(m_lock);
                if( m_reqQue.empty() )
                {
                    break;
                }
                _req = m_reqQue.front();
                m_reqQue.pop_front();
            }
            _DoPostData( _req );
        }
    }
    LOG_INFO( "CCurlObject End" );
}

void CCurlObject::PostRequest(const std::string &url, const std::string &postdata, s32 timeOut)
{
    curlReq req;
    req.url = url;
    req.postdata = postdata;
    req.timeOut = timeOut;
    _DoAddRequest( req );
}

void CCurlObject::PostRequest4Data(const std::string &url, const std::string &postdata, OnDataStart onStart, OnData onData, OnDataEnd onEnd, const std::string &userdata, s32 timeOut)
{
    curlReq req;
    req.url = url;
    req.postdata = postdata;
    req.timeOut = timeOut;

    req.needReadData = true;
    req.userdata = userdata;
    req.onDataStart = onStart;
    req.onData = onData;
    req.onDataEnd = onEnd;
    _DoAddRequest( req );
}

void CCurlObject::Download(const std::string &url, const std::string &postdata, OnDataStart onStart, OnData onData, OnDataEnd onEnd, OnProgress onProgress, const std::string &userdata, s32 timeOut)
{
    curlReq req;
    req.url = url;
    req.postdata = postdata;
    req.timeOut = timeOut;

    req.needReadData = true;
    req.userdata = userdata;
    req.onDataStart = onStart;
    req.onData = onData;
    req.onDataEnd = onEnd;

    req.needProgress = true;
    req.onProgress = onProgress;
    _DoAddRequest( req );
}

void CCurlObject::_DoAddRequest(curlReq &req)
{
    {
        CGuardLock<CLock> g(m_lock);
        m_reqQue.push_back( req );
    }
    m_workSignal.SetEvent();
}

void CCurlObject::_DoPostData(curlReq &req)
{
    LOG_DEBUG( "CCurlObject DoPost url[%s] data[%s]", req.url.c_str(), req.postdata.c_str() );

    CURL *curl = curl_easy_init();
    if( curl == NULL )
    {
        LOG_ERROR( "CCurlObject curl_easy_init failed. url[%s] data[%s]", req.url.c_str(), req.postdata.c_str() );
        return;
    }
    
    if( req.needReadData && req.onDataStart )
    {
        req.onDataStart( req.userdata );
    }

    bool ret = false;
    do
    {
        CURLcode res = curl_easy_setopt( curl, CURLOPT_TIMEOUT, req.timeOut );
        if( res != CURLE_OK ) break;
        // forbidin ALARM
        curl_easy_setopt( curl, CURLOPT_NOSIGNAL, 1L );
        // no CLOSE_WAIT
        curl_easy_setopt( curl, CURLOPT_FORBID_REUSE, 1 );

        res = curl_easy_setopt( curl, CURLOPT_URL, req.url.c_str() );
        if( res != CURLE_OK ) break;

        // cancel Expect
        curl_slist *plist = curl_slist_append( NULL, "Expect:" );
        if( !plist ) break;
        res = curl_easy_setopt( curl, CURLOPT_HEADER, plist );
        if( res != CURLE_OK ) break;

        /*
        curl_slist *plist = curl_slist_append( NULL, "Content-Type:application/json;charset=UTF-8" );
        if( !plist ) break;

        res = curl_easy_setopt( curl, CURLOPT_HEADER, plist );
        if( res != CURLE_OK ) break;

        res = curl_easy_setopt( curl, CURLOPT_POSTFIELDS, req.postdata.c_str() );
        if( res != CURLE_OK ) break;
        //*/
        
        res = curl_easy_setopt( curl, CURLOPT_POSTFIELDS, req.postdata.c_str() );
        if( res != CURLE_OK ) break;

        res = curl_easy_setopt( curl, CURLOPT_FOLLOWLOCATION, 1 );
        if( res != CURLE_OK ) break;

        res = curl_easy_setopt( curl, CURLOPT_MAXREDIRS, 5 ); 
        if( res != CURLE_OK ) break;

        if( req.needReadData )
        {
            res = curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, _curl_writeCallback );
            if( res != CURLE_OK ) break;

            res = curl_easy_setopt( curl, CURLOPT_WRITEDATA, &req );
            if( res != CURLE_OK ) break;
        }

        if( req.needProgress )
        {
            res = curl_easy_setopt( curl, CURLOPT_NOPROGRESS, 0 );
            if( res != CURLE_OK ) break;

            res = curl_easy_setopt( curl, CURLOPT_PROGRESSFUNCTION, _curl_progressCallback );
            if( res != CURLE_OK ) break;
            
            res = curl_easy_setopt( curl, CURLOPT_PROGRESSDATA, &req );
            if( res != CURLE_OK ) break;
        }

        res = curl_easy_perform( curl );
        if( res != CURLE_OK ) break;

        ret = true;
    } while(0);

    curl_easy_cleanup( curl );
        
    if( req.needReadData && req.onDataEnd )
    {
        req.onDataEnd( req.userdata, req.rsp );
    }

    if( !ret )
    {
        LOG_ERROR( "CCurlObject do failed. url[%s] data[%s]", req.url.c_str(), req.postdata.c_str() );
    }
}

size_t CCurlObject::_curl_writeCallback(void *ptr, size_t size, size_t nmemb, void *stream)
{
    curlReq *req = (curlReq*)stream;
    size_t sz = size * nmemb;
    req->rsp.append( (char*)ptr, sz );
    if( req->needReadData && req->onData )
    {
        req->onData( req->userdata, ptr, size, nmemb );
    }
    return sz;
}

int CCurlObject::_curl_progressCallback(void *client, double dltotal, double dlnow, double ultotal, double ulnow)
{
    curlReq *req = (curlReq*)client;
    if( req->needProgress && req->onProgress )
    {
        req->onProgress( req->userdata, dltotal, dlnow, ultotal, ulnow );
    }
    return 0;
}

////////////////////////////////////////////////////////////////////////////


void CCurlManager::Init(s32 count)
{
    m_count = count;
    if( count < 0 || count > 128 )
    {
        m_count = 0;
    }
    m_idx = 0;
    m_curls = NULL;
}

bool CCurlManager::Start()
{
    if( m_count == 0 )
    {
        // not use curl
        return true;
    }

    curl_global_init( CURL_GLOBAL_ALL );

    m_curls = new CCurlObject*[ m_count ];
    memset( m_curls, 0, m_count * sizeof(CCurlObject*) );
    for( s32 i=0; i<m_count; ++i )
    {
        m_curls[i] = new CCurlObject();
        if( !m_curls[i]->Start() )
        {
            LOG_ERROR( "CCurlManager [%s][%s][%d] start failed", 
                __FILE__, __FUNCTION__, __LINE__ );
            return false;
        }
    }
    return true;
}

void CCurlManager::Stop()
{
    if( m_count == 0 )
    {
        return;
    }

    if( !m_curls )
    {
        return;
    }

    for( s32 i=0; i<m_count; ++i )
    {
        if( m_curls[i] )
        {
            m_curls[i]->Stop();
            delete m_curls[i];
            m_curls[i] = NULL;
        }
    }
    delete [] m_curls;

    curl_global_cleanup();
}

void CCurlManager::PostRequest(const std::string &url, const std::string &postdata, s32 timeOut)
{
   CCurlObject *curl = _getNextCurlObject();
   SELF_ASSERT( curl, return; );
   curl->PostRequest( url, postdata, timeOut );
}

void CCurlManager::PostRequest4Data(const std::string &url, const std::string &postdata, OnDataStart onStart, OnData onData, OnDataEnd onEnd, const std::string &userdata, s32 timeOut)
{
   CCurlObject *curl = _getNextCurlObject();
   SELF_ASSERT( curl, return; );
   curl->PostRequest4Data( url, postdata, onStart, onData, onEnd, userdata, timeOut );
}

void CCurlManager::Download(const std::string &url, const std::string &postdata, OnDataStart onStart, OnData onData, OnDataEnd onEnd, OnProgress onProgress, const std::string &userdata, s32 timeOut)
{
   CCurlObject *curl = _getNextCurlObject();
   SELF_ASSERT( curl, return; );
   curl->Download( url, postdata, onStart, onData, onEnd, onProgress, userdata, timeOut );
}

CCurlObject* CCurlManager::_getNextCurlObject()
{
    s32 idx = 0;
    {
        CGuardLock<CLock> g(m_lock);
        idx = m_idx++;
        if( m_idx >= m_count )
        {
            m_idx = 0;
        }
    }
    return m_curls[idx];
}


#endif


